﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Data.Objects;
using DataAccess.Common;
using System.Web;
using System.Data.Common;
using System.Data.EntityClient;
using Infrastructure;
using System.Data.Objects.DataClasses;

namespace DataAccess
{
    public class EFDBContext:IDBContext
    {
      private const string NoContext = "ObjectContext must be initialized.";
      private const string NoTransToCommit = "No Transaction can be commited.";
      private const string NoTransToRollBack = "No Transaction can be Rollback.";

        public UIEnum UIFlag
        {
            set;
            get;
        }

        private EFExpert Expert
        {
            set;
            get;
        }

        public EFDBContext(UIEnum UIFlag,ObjectContext Context)
        {
            this.UIFlag = UIFlag;
            if (UIFlag == UIEnum.Web)
                _contextInSession = Context;
            else
                _context = Context;
            Expert = EFExpert.Instance(Context);
        }

        private EFDBContext() { }
       
        private static ObjectContext _context;  //Used when UIFlag is Form

        private ObjectContext _contextInSession  //Used when UIFlag is Web
        {
            get { return HttpContext.Current.Items["Context"] as ObjectContext; }
            set { HttpContext.Current.Items["Context"] = value; }
        }

        public ObjectContext GetContext()
        {
            if (UIFlag == UIEnum.Web)
            {
                if (_contextInSession == null)
                    throw new Exception(NoContext);
                else
                    return _contextInSession;
            }
            else
            {
                if (_context == null)
                    throw new Exception(NoContext);
                else
                    return _context;
            }
         
         }

        public void Submit()
        {
            try
            {
                GetContext().SaveChanges();
                GetContext().AcceptAllChanges();
            }
            catch (Exception ex)
            {
                Exception newEx = new Exception("Submit Failed!", ex);
            }
        }

        public void InsertObject(Object newObj)
        {
            if (Expert.GetObjectState(newObj) == EntityState.Detached)
            {
                string entitySetName = Expert.GetEntitySetName(newObj);
                GetContext().AddObject(entitySetName, newObj);
                Submit();
            }
            else
                throw new NotSupportedException("Entity in EntityState is not supported!");
        }

        public void UpdateObject(object obj)
        {
            EntityState state = Expert.GetObjectState(obj);
            if (state == EntityState.Modified || state == EntityState.Unchanged)
                Submit();
            else if (state == EntityState.Detached)
            {
                if (Expert.GetEntityKey(obj) != null)
                {
                    GetContext().ApplyPropertyChanges(Expert.GetEntitySetName(obj), obj);
                    Submit();
                }
                else
                    throw new Exception("Update Failed! since its Key is null");
            }
            else
            {
                throw new NotSupportedException("Entity in EntityState is not supported!");
            }
        }

        public void DeleteObject(Object existedObj)
        {
            EntityKey key = Expert.GetEntityKey(existedObj);
            if (key == null)
                throw new Exception("Delete failed,the key can not be null in entity");
            //delete all descendents objects in memory
            List<object> children=new List<object>();
            Expert.GetChildrenEntities(existedObj, null, children);
            children = children.Where(c => Expert.GetObjectState(c) != EntityState.Detached).ToList();
            foreach (var o in children)
            {
                markDeletion(o);
            }
            //submit the sql to database.

        }

        private void markDeletion(object entity)
        {
            EntityKey key = Expert.GetEntityKey(entity);
            if (key != null)
            {
                ObjectStateEntry ose = GetContext().ObjectStateManager.GetObjectStateEntry(key);
                if (ose != null)
                    ose.Delete();
            }
        }

        private void markModification(object entity)
        {
            EntityKey key = Expert.GetEntityKey(entity);
            if (key != null)
            {
                ObjectStateEntry ose = GetContext().ObjectStateManager.GetObjectStateEntry(key);
                if (ose != null)
                    ose.SetModified();
            }
        }

        public void Detach(object entity)
        {
            if (Expert.GetEntityKey(entity) != null)
                GetContext().Detach(entity);
            else
                throw new Exception("Delete failed,the key can not be null in entity");
        }

        private object Exception(string p)
        {
            throw new NotImplementedException();
        }


        private DbTransaction transaction;

        public void BeginTransaction()
        {
            
            if (GetContext().Connection.State != ConnectionState.Open)
            {
                GetContext().Connection.Open();
            }
            transaction = GetContext().Connection.BeginTransaction();
        }

        public void CommitTransaction()
        {
            if (transaction == null)
                throw new Exception(NoTransToCommit);
            else
                transaction.Commit();
        }

        public void RollBackTransaction()
        {
            if (transaction == null)
                throw new Exception(NoTransToRollBack);
            else
                transaction.Rollback();
        }




        public void ExecuteSQLNonQuery(string sql)
        {
            DbCommand cmd = new SqlCommand();
            CommandType cmdType = CommandType.Text;
            DbConnection conn = GetContext().Connection;
            PrepareCommand(cmd, conn,transaction, cmdType, sql, null);
            cmd.ExecuteNonQuery();
            cmd.Parameters.Clear();
        }

        public List<T> ExecuteSQLQuery<T>(string sql)
        {
            DataTable table = ExecuteSQLQuery(sql);
            return DataTableConverter.ConvertTable2Object<T>(table);
        }

        public DataTable ExecuteSQLQuery(string sql)
        {
            DbCommand cmd = new SqlCommand();
            CommandType cmdType = CommandType.Text;
            DbConnection conn = GetContext().Connection;
            PrepareCommand(cmd, conn, transaction, cmdType, sql, null);
            DbDataAdapter adaper = new SqlDataAdapter();
            adaper.SelectCommand = cmd;
            DataSet set = new DataSet();
            adaper.Fill(set);
            DataTable table = set.Tables[0];
            cmd.Parameters.Clear();
            return table;
        }

        public void ExecuteStoreProcedure(string spName, params SqlParameter[] spParameters)
        {
            DbCommand cmd = new SqlCommand();
            CommandType cmdType = CommandType.StoredProcedure;
            DbConnection conn = GetContext().Connection;
            PrepareCommand(cmd, conn, transaction, cmdType, spName, spParameters);
            cmd.ExecuteNonQuery();
            cmd.Parameters.Clear();
        }

        public List<T> ExecuteQueryStoreProcedure<T>(string spName, params SqlParameter[] spParameters)
        {
            DataTable table = ExecuteQueryStoreProcedure(spName, spParameters);
            return DataTableConverter.ConvertTable2Object<T>(table);
        }

        public DataTable ExecuteQueryStoreProcedure(string spName, params SqlParameter[] spParameters)
        {
            DbCommand cmd = new SqlCommand();
            CommandType cmdType = CommandType.StoredProcedure;
            DbConnection conn = GetContext().Connection;
            PrepareCommand(cmd, conn, transaction, cmdType, spName, spParameters);

            DbDataAdapter adaper = new SqlDataAdapter();
            adaper.SelectCommand = cmd;
            DataSet set = new DataSet();
            adaper.Fill(set);
            DataTable table = set.Tables[0];
            cmd.Parameters.Clear();
            return table;
        }

        public void FinalizeContext()
        {
            if (UIFlag == UIEnum.Web)
            {
                if (_contextInSession != null)
                {
                    _contextInSession.Dispose();
                    _contextInSession = null;
                }
            }
            else
            {
                if (_context != null)
                {
                    _context.Dispose();
                    _context = null;
                }
            }
        }

        private void PrepareCommand(DbCommand cmd, DbConnection conn, DbTransaction trans, CommandType cmdType, string cmdText, SqlParameter[] cmdParms)
        {

            if (conn.State != ConnectionState.Open)
                conn.Open();

            cmd.Connection = conn;
            cmd.CommandText = cmdText;

            if (trans != null)
                cmd.Transaction = trans;

            cmd.CommandType = cmdType;
            if (cmdParms != null)
            {
                foreach (SqlParameter parm in cmdParms)
                    cmd.Parameters.Add(parm);
            }
        }

    }
}
